#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <getopt.hpp>
#include <random.hpp>

static inline bool checkStringEmpty(const std::string &str){
    if (str.empty()){
        return true;
    }
    for(size_t i=0;i<str.size();i++){
        if(!isspace(str.at(i))){
            return false;
        }
    }
    return true;
}

int readfile(const std::vector<std::istream *> &is,std::vector<std::string *> &plines){
    std::string *tempbuf=new std::string;
    plines.clear();
    for(size_t i=0;i<is.size();i++){
        while(std::getline(*is.at(i),*tempbuf)){
            if (!checkStringEmpty(*tempbuf)){
                plines.push_back(tempbuf);
                tempbuf=new std::string;
            }
        }
    }
    return 0;
}

void shuffle_data(const std::vector<std::istream *> &in,const unsigned *pseed,std::ostream &out){
    unsigned seed;
    if (pseed){
        seed=*pseed;
    }else{
        SystemRandom sysrand;
        seed=sysrand.next();
    }
    Mt19937Random mt19937(seed);
    std::vector<std::string *> lines;
    readfile(in,lines);
    std::random_shuffle(lines.begin(),lines.end(),mt19937);
    out<<"Random seed: "<<std::hex<<seed<<'\n';
    for(size_t i=0;i<lines.size();i++){
        std::string *str=lines.at(i);
        out<<*str<<'\n';
        delete str;
    }
    out.flush();
}

void sample_data(const std::vector<std::istream *> &in,size_t count,const unsigned *pseed,std::ostream &out){
    std::string *target=new std::string[count];
    std::string line;
    unsigned processed=0;
    unsigned seed;
    if (pseed){
        seed=*pseed;
    }else{
        SystemRandom sysrand;
        seed=sysrand.next();
    }
    Mt19937Random mt19937(seed);
    for(size_t i=0;i<in.size();i++){
        while(std::getline(*in.at(i),line)){
            if (checkStringEmpty(line)){
                continue;
            }
            if (processed<count){
                target[processed]=line;
            }else{
                unsigned rd=mt19937(processed);
                if (rd<count){
                    target[rd]=line;
                }
            }
            ++processed;
        }
    }
    out<<"Random seed: "<<std::hex<<seed<<'\n';
    size_t total=processed>count?count:processed;
    for (size_t i=0;i<total;i++){
        out<<target[i]<<'\n';
    }
    out.flush();
    delete[] target;
}

void printHelp(){
    std::cout<<"Help"<<std::endl;
}
void printVersion(){
    std::cout<<"Version"<<std::endl;
}

int main(int argc,char **argv){
    struct option long_opts[]={
        {"help",no_argument,NULL,'h'},
        {"version",no_argument,NULL,'v'},
        {"output",required_argument,NULL,'o'},
        {"shuffle",no_argument,NULL,'u'},
        {"sample",no_argument,NULL,'s'},
        {"count",required_argument,NULL,'n'},
        {"seed",required_argument,NULL,'e'},
        {NULL,0,NULL,0},
    };
    getoptions options(argc,argv,long_opts);
    if (options.error()){
        return -1;
    }
    const std::map<std::string,std::string> &arguments=options.argument();
    std::map<std::string,std::string>::const_iterator iter,iter2,outiter;
    unsigned seed,*pseed=NULL;
    if (arguments.find(std::string("help"))!=arguments.end()){
        printHelp();
        return 0;
    }else if(arguments.find(std::string("version"))!=arguments.end()){
        printVersion();
        return 0;
    }
    if ((iter=arguments.find(std::string("seed")))!=arguments.end()){
        std::string seedstr;
        if (iter->second.size()>8){
            seedstr=iter->second.substr(iter->second.size()-8);
        }else{
            seedstr=iter->second;
        }
        char *illegal;
        seed=strtoul(seedstr.c_str(),&illegal,16);
        if (seedstr.c_str()+seedstr.size()==illegal){
            pseed=&seed;
        }
    }
    iter=arguments.find(std::string("shuffle"));
    iter2=arguments.find(std::string("sample"));
    if (iter!=arguments.end() && iter2!=arguments.end()){
        std::cerr<<"Shuffling and sampling are conflict"<<std::endl;
        return -1;
    }else if(iter==arguments.end() && iter2==arguments.end()){
        std::cerr<<"No random method specified"<<std::endl;
        return -1;
    }
    int ret=0;
    std::ostream *output=&std::cout;
    std::vector<std::istream*> inputs;
    const std::vector<std::string> &nonarguments=options.nonoption_argument();
    for(size_t i=0;i<nonarguments.size();i++){
        inputs.push_back(new std::ifstream(nonarguments.at(i).c_str()));
    }
    if (inputs.empty()){
        inputs.push_back(&std::cin);
    }
    if((outiter=arguments.find(std::string("output")))!=arguments.end()){
        output=new std::ofstream(outiter->second.c_str());
    }
    if (iter!=arguments.end()){
        shuffle_data(inputs,pseed,*output);
    }else if(iter2!=arguments.end()){
        iter2=arguments.find("count");
        if (iter2==arguments.end()){
            std::cerr<<"No -n or --count option found"<<std::endl;
            ret=-1;
        }else{
            char *illegal;
            size_t count=strtoul(iter2->second.c_str(),&illegal,10);
            if (illegal==iter2->second.c_str()+iter2->second.size() && count!=0){
                sample_data(inputs,count,pseed,*output);
            }else{
                std::cerr<<"Illegal count `"<<count<<"` specified"<<std::endl;
                ret=-1;
            }
        }
    }
    if (output!=&std::cout){
        delete output;
    }
    for(size_t i=0;i<inputs.size();i++){
        if (inputs.at(i)!=&std::cin){
            delete inputs.at(i);
        }
    }
    return ret;
}
